package pers.cz.postgirl.adapter.mybatis;

import com.alibaba.fastjson.JSON;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import pers.cz.jefconfig.tools.StringUtils;
import pers.cz.postgirl.adapter.AbstractTransformer;
import pers.cz.postgirl.config.EvenType;
import pers.cz.postgirl.config.MybatisSqlSessionFactory;
import pers.cz.postgirl.core.EnhanceModel;
import pers.cz.postgirl.core.common.CpublicFuncCallBack;
import pers.cz.postgirl.core.common.ParamFilter;
import pers.cz.postgirl.entity.AutoCollectMethodParamsMapper;
import pers.cz.postgirl.entity.AutoCollectMethodParamsWithBLOBs;
import pers.cz.postgirl.util.ContextUtils;

import java.util.Map;

/**
 * @program: postgirl-client
 * @description: mybatis增强
 * @author: Cheng Zhi
 * @create: 2023-05-23 14:54
 **/
public class MybatisPlugin extends AbstractTransformer {

    public void loadEnhanceModel() {
        enhanceModel = EnhanceModel.builder()
                .classPattern("org.apache.ibatis.binding.MapperMethod")
                .methodPatterns(getMethodPattern())
                .build();
    }

    private EnhanceModel.MethodPattern[] getMethodPattern() {

        // 添加自定义插码内容
        EnhanceModel.MethodPattern.DefaultCodePattern defaultCodePattern = EnhanceModel.MethodPattern.DefaultCodePattern.builder()
                .beforeCode(getBeforeSrc())
                .returnCode(getEndSrc())
                .build();

        EnhanceModel.MethodPattern methodBefore = EnhanceModel.MethodPattern.builder()
                .methodName("execute")
                .paramTypes(new String[] {"org.apache.ibatis.session.SqlSession", "java.lang.Object[]"})
                .evenTypes(new EvenType[] {EvenType.BEFORE, EvenType.RETURN})
                .defaultCodePattern(defaultCodePattern)
                .build();
        return new EnhanceModel.MethodPattern[] {methodBefore};
    }


    /**
     * 默认方法入口插码，获取方法入参
     * @return
     */
    private CpublicFuncCallBack getBeforeSrc(){

        return new CpublicFuncCallBack() {

            private CtMethod ctMethod;
            private String ctClassName;

            @Override
            public String execute() throws Exception {
                StringBuilder sb = new StringBuilder();
                sb.append("try {\n");
                sb.append("          pers.cz.postgirl.core.Spy spy = new pers.cz.postgirl.core.Spy();\n");
                sb.append("          spy.setStartTime(System.currentTimeMillis());\n");
                sb.append("          spy.setMethodName(");
                sb.append(getString(ctMethod.getName()) + ");\n");
                sb.append("          spy.setClzName(");
                sb.append(getString(ctClassName) + ");\n");
                sb.append(createDumpParams(ctMethod.getParameterTypes()));
                sb.append("          spy.setDataType(pers.cz.postgirl.core.Spy.DataType.INPUT);\n");

                // 判断是否需要回放，如果需要回放，则直接去匹配
                sb.append("          if(pers.cz.postgirl.core.SpyHandler.judgePlayback()) {\n");
                sb.append("              pers.cz.postgirl.core.Spy myResult$ = pers.cz.postgirl.core.SpyHandler.getResult(spy);\n");
                sb.append(transReturn(ctMethod.getReturnType().getName()));
                sb.append("          }\n");
                sb.append("          pers.cz.postgirl.core.SpyHandler.onHandle(spy);\n");
                sb.append("        } catch (Exception e) {\n");
                sb.append("            e.printStackTrace();\n");
                sb.append( "        }");

                return sb.toString();
            }

            @Override
            public CpublicFuncCallBack setParamters(Map<String, Object> map) throws Exception {

                this.ctMethod = (CtMethod) map.get("CTMETHOD");
                this.ctClassName = (String) map.get("CTCLASSNAME");

                return this;
            }
        };
    }



    private String createDumpParams(CtClass[] parameterTypes) {
        StringBuilder sb = new StringBuilder();
        if (parameterTypes != null && parameterTypes.length > 0) {
            for (int i = 0; i < parameterTypes.length; i++) {

                if (!ParamFilter.isNeedfilterParam(parameterTypes[i].getName())) {
                    sb.append("          spy.addParam(\"");
                    sb.append(parameterTypes[i].getName());
                    sb.append("\",$");
                    sb.append(i + 1);
                    sb.append(");\n");
                }
            }
        }
        // mybatis比较特殊，为了增加对比时的准确性，新增Mapper.xml中的id 和 type ，<select id="selectByExample" resultMap="BaseResultMap" parameterType="pers.cz.model.AutoCollectMethodParamsExample"></select>
        sb.append("            spy.addParam(this.command.getType());\n");
        sb.append("            spy.addParam(this.command.getName());\n");
        return sb.toString();
    }

    /**
     * 具体插入的代码
     * @return  插码结果
     */
    private CpublicFuncCallBack getEndSrc()  {

        return new CpublicFuncCallBack() {

            private CtMethod ctMethod;
            private String ctClassName;

            @Override
            public String execute() throws Exception {
                StringBuilder sb = new StringBuilder();
                sb.append("try{\n");
                sb.append("     pers.cz.postgirl.core.Spy spy = new pers.cz.postgirl.core.Spy();\n");
                sb.append("     spy.setClzName(" + getString(ctMethod.getDeclaringClass().getName()) + ");\n");
                sb.append("     spy.setMethodName(" + getString(ctMethod.getName()) + ");\n");
                sb.append("     spy.setEndTime(System.currentTimeMillis());");
                sb.append("     spy.setDataType(pers.cz.postgirl.core.Spy.DataType.OUTPUT);\n");
                //sb.append("     spy.setRealTypeName($_.getClass().getName());\n");
                sb.append("     spy.setGenericsTypeName(pers.cz.postgirl.util.ObjUtils.getGenericsTypeName($_));\n");
                String rtnTypeName = null;
                try {
                    rtnTypeName = ctMethod.getReturnType().getName();
                }catch (NotFoundException e){
                    e.printStackTrace();
                }
                sb.append(createDumpReturnValue(rtnTypeName));
                sb.append("     pers.cz.postgirl.core.SpyHandler.onHandle(spy);\n");
                sb.append("}catch(Throwable a){\n");
                sb.append("     a.printStackTrace();\n");
                sb.append("}\n");
                return sb.toString();
            }

            @Override
            public CpublicFuncCallBack setParamters(Map<String, Object> map) throws Exception {

                this.ctMethod = (CtMethod) map.get("CTMETHOD");
                this.ctClassName = (String) map.get("CTCLASSNAME");

                return this;
            }
        };
    }

    private String transReturn(String returnType) {
        if("int".equals(returnType)){
            return "              return Integer.parseInt(myResult$.getReturnValue().toString());\n";
        }else if("long".equals(returnType)){
            return "              return Long.parseLong(myResult$.getReturnValue().toString());\n";
        }else if("float".equals(returnType)){
            return "              return Float.parseFloat(myResult$.getReturnValue().toString());\n";
        }else if("double".equals(returnType)){
            return "              return Double.parseDouble(myResult$.getReturnValue().toString());\n";
        }else if("byte".equals(returnType)){
            return "              return Byte.parseByte(myResult$.getReturnValue().toString());\n";
        }else if("short".equals(returnType)){
            return "              return Short.parseShort(myResult$.getReturnValue().toString());\n";
        }else if("char".equals(returnType)){
            return "              return myResult$.getReturnValue().toString().charAt(0);\n";
        }else if("boolean".equals(returnType)){
            return "              return Boolean.parseBoolean(myResult$.getReturnValue().toString());\n";
        }else if("void".equals(returnType)) {
            return "              return;\n";
        } else if("java.lang.Object".equals(returnType)) {
            // return  "             return pers.cz.postgirl.util.JsonUtils.spyToObj(myResult$);\n";
            return  "             return myResult$.getReturnValue();\n";
        } else{
            return "              return ("+returnType+")myResult$.getReturnValue();\n"; // 如果使用fastJSON直接强转可能会报错
        }
    }
    /**
     * 创建出参的插码
     * @param returnType    返回值类型
     * @return  插码结果
     */
    private String createDumpReturnValue(String returnType) {
        StringBuilder sb = new StringBuilder();
        if ("void".equals(returnType)) {
            sb.append("spy.putReturnValue(");
            sb.append(getString(returnType));
            sb.append(",").append(getString("")).append(");\n");
        } else {
            sb.append("spy.putReturnValue(")
                    .append(getString(returnType))
                    .append(",$_);\n");
        }
        return sb.toString();
    }

    private String getString(String data) {
        return "\"" + data + "\"";
    }

}
